package com.github.blanexie.nexusj.torrent.domain.valobj

import cn.hutool.core.convert.Convert
import cn.hutool.core.io.FileUtil
import cn.hutool.crypto.digest.DigestUtil
import com.dampcake.bencode.Type
import com.github.blanexie.nexusj.bencode.bencode
import com.github.blanexie.nexusj.support.UTF8
import com.github.blanexie.nexusj.support.setting
import java.io.File
import java.nio.ByteBuffer


/**
 *  种子文件的信息
 */
data class TorrentFile(
    val infoHash: String,
    val announce: String?,  //： Tracker的主服务器
    val announceList: List<String>?, //Tracker服务器列表;
    val comment: String?,//：种子文件的注释
    val commentUtf8: String?, //：种子文件注释的utf-8编码
    val creationDate: Long?,//：种子文件建立的时间，是从1970年1月1日00:00:00到现在的秒数。
    val encoding: String?, //：种子文件的默认编码，比如GB2312，Big5，utf-8等
    val nodes: List<String>?,//：这个字段包含一系列ip和相应端口的列表，是用于连接DHT初始node
    val info: Info
) {
    init {
        if (info == null) {
            throw Exception("种子文件中的info的格式不正确")
        }
    }

    /**
     * 持久化成文件
     */
    fun cacheFile(): File {
        val path = setting["torrent.path"]
        val file = File("$path/$infoHash/$infoHash.torrent")
        if (file.exists()) {
            return file;
        }
        val infoMap = hashMapOf<String, Any>()
        infoMap["name"] = info.name
        info.nameUtf8?.let {
            infoMap.put("name.utf-8", it)
        }
        info.pieceLength?.let {
            infoMap.put("piece length", it)
        }
        info.pieces?.let {
            infoMap.put("pieces", it.array())
        }
        info.publisher?.let {
            infoMap.put("publisher", it)
        }
        info.publisherUtf8?.let {
            infoMap.put("publisher.utf-8", it)
        }
        info.publisherUrl?.let {
            infoMap.put("publisher-url", it)
        }
        info.publisherUrlUtf8?.let {
            infoMap.put("publisher-url.utf-8", it)
        }
        if (info.files == null || info.files.isEmpty()) {
            info.files?.let { it ->
                val list = it.map {
                    mapOf("path" to it.path, "length" to it.length, "path.utf-8" to it.pathUtf8)
                }
                infoMap.put("files", list)
            }
        } else {
            info.length?.let {
                infoMap.put("length", it)
            }
        }
        val torrentMap = mapOf(
            "announce" to setting["announce"],
            "announce-list" to announceList,
            "comment" to comment,
            "comment.utf-8" to commentUtf8,
            "creation date" to creationDate,
            "encoding" to encoding,
            "nodes" to nodes,
            "info" to infoMap
        )
        val encode = bencode.encode(torrentMap)
        return FileUtil.writeBytes(encode, file)
    }

    companion object {
        /**
         *  解析种子文件, 并生成对应的对象
         */
        fun create(torrent: ByteArray): TorrentFile {
            val torrentMap = bencode.decode(torrent, Type.DICTIONARY)

            val infoMap = torrentMap["info"]!!.toMap()
            //计算infohash值
            val infoBytes = bencode.encode(infoMap)
            val infoHash = DigestUtil.sha1Hex(infoBytes)
            val announce: String? = torrentMap["announce"]?.toStr()
            val announceList: List<String>? = torrentMap["announce-list"]?.toListStr()
            val comment: String? = torrentMap["comment"]?.toStr()
            val commentUtf8: String? = torrentMap["comment.utf-8"]?.toStr()
            val creationDate: Long? = torrentMap["creation_date"]?.toLong()
            val encoding: String? = torrentMap["encoding"]?.toStr()
            val nodes: List<String>? = torrentMap["nodes"]?.toListStr()

            val name: String = infoMap["name"]!!.toStr()
            var length: Long? = infoMap["length"]?.toLong()
            val nameUtf8: String? = infoMap["name.utf-8"]?.toStr()
            val pieceLength: Long = infoMap["piece length"]!!.toLong()
            val pieces: ByteBuffer = infoMap["pieces"] as ByteBuffer
            val publisher: String? = infoMap["publisher"]?.toStr()
            val publisherUtf8: String? = infoMap["publisher.utf-8"]?.toStr()
            val publisherUrl: String? = infoMap["publisher-url"]?.toStr()
            val publisherUrlUtf8: String? = infoMap["publisher-url.utf-8"]?.toStr()
            val files = infoMap["files"]?.toList()?.map {
                it?.toMap().run {
                    val path = this!!["path"]!!.toListStr()
                    val pathUtf8 = this["path.utf-8"]?.toListStr()
                    val length = this["length"]!!.toLong()
                    Files(length, path, pathUtf8)
                }
            }

            val info = Info(
                length, files, name, nameUtf8, pieceLength, pieces,
                publisher, publisherUtf8, publisherUrl, publisherUrlUtf8,
            )
            return TorrentFile(
                infoHash, announce, announceList, comment, commentUtf8, creationDate, encoding, nodes, info
            )
        }
    }
}

/**
 * 种子中的info信息
 */
data class Info(
    var length: Long?, //：文件的总大小（Byte）
    val files: List<Files>?, //多文件中的文件   //files 和 length 只能有一个
    val name: String, //：推荐的文件夹名，此项可于下载时更改
    val nameUtf8: String?, //：推荐的文件夹名的utf-8编码，同上
    val pieceLength: Long, //：每个文件块的大小（Byte）
    val pieces: ByteBuffer, //：文件的特征信息（将所有文件按照piece length的字节大小分成块，每块计算一个SHA1值，然后将这些值连接起来所组成）
    val publisher: String?, //：文件发布者的名字
    val publisherUtf8: String?, //：文件发布者的名字的utf-8编码
    val publisherUrl: String?,//：文件发布者的网址
    val publisherUrlUtf8: String?, //：文件发布者网址的utf-8编码

) {
    init {
        if (length == null && (files == null || files.isEmpty())) {
            throw Exception("种子文件中的info的格式不正确")
        }
        //计算文件的大小
        if (length == null) {
            this.length = this.files!!.sumOf { it.length }
        }
    }
}

/**
 * 种子中的info信息
 */
data class Files(
    val length: Long,
    val path: List<String>,
    val pathUtf8: List<String>?,//文件名的UTF-8编码，同上
)


fun Any.toLong(): Long {
    return Convert.toLong(this)
}

fun Any.toStr(): String {
    if (this is ByteBuffer) {
        return UTF8.decode(this).toString()
    }
    return toString()
}

fun Any.toListStr(): List<String> {
    if (this is List<*>) {
        return this.map { it!!.toStr() }
    }
    throw Exception("类型不对,无法转换")
}

fun Any.toList(): List<*> {
    if (this is List<*>) {
        return this
    }
    throw Exception("类型不对,无法转换")
}

fun Any.toMap(): Map<String, *> {
    val ret = hashMapOf<String, Any>()
    if (this is Map<*, *>) {
        this.forEach { k, v ->
            ret[k!!.toStr()] = v!!
        }
        return ret
    }
    throw Exception("类型不对,无法转换")
}
